(*  :Title:	Digital Signal Processing Analysis  *)

(*  :Authors:	Brian Evans, James McClellan  *)

(*
    :Summary:	Perform basic analysis on a one-dimensional discrete-time
		function, including plotting f[n] and graphing the poles
		and zeroes of F[z].
 *)

(*  :Context:	SignalProcessing`Digital`DSPAnalyze`  *)

(*  :PackageVersion:  2.7	*)

(*
    :Copyright:	Copyright 1989-1991 by Brian L. Evans
		Georgia Tech Research Corporation

	Permission to use, copy, modify, and distribute this software
	and its documentation for any purpose and without fee is
	hereby granted, provided that the above copyright notice
	appear in all copies and that both that copyright notice and
	this permission notice appear in supporting documentation,
	and that the name of the Georgia Tech Research Corporation,
	Georgia Tech, or Georgia Institute of Technology not be used
	in advertising or publicity pertaining to distribution of the
	software without specific, written prior permission.  Georgia
	Tech makes no representations about the suitability of this
	software for any purpose.  It is provided "as is" without
	express or implied warranty.
 *)

(*  :History:	*)

(*  :Keywords:	analysis, transforms, signals and systems	*)

(*  :Source:	*)

(*  :Warning:	*)

(*  :Mathematica Version:  1.2 or 2.0  *)

(*  :Limitation:  *)

(*  :Discussion:  *)

(*  :Functions:	DSPAnalyze  *)



(*  B E G I N     P A C K A G E  *)

BeginPackage[ "SignalProcessing`Digital`DSPAnalyze`",
	      "SignalProcessing`Digital`DTFT`",
	      "SignalProcessing`Digital`DSupport`",
	      "SignalProcessing`Digital`ZTransform`",
	      "SignalProcessing`Digital`ZSupport`",
	      "SignalProcessing`Support`TransSupport`",
	      "SignalProcessing`Support`ROC`",
	      "SignalProcessing`Support`SigProc`",
	      "SignalProcessing`Support`SupCode`" ]


If [ TrueQ[ $VersionNumber >= 2.0 ],
     Off[ General::spell ];
     Off[ General::spell1 ] ];


(*  U S A G E     I N F O R M A T I O N  *)

DSPAnalyze::usage =
	"DSPAnalyze[f, n] and DSPAnalyze[f, n, start, end] \
	will plot the real values of f[n] from n = Floor[start] to \
	n = Ceiling[end], print its z-transform, indicate conditions \
	for stability, display the pole-zero diagram, and plot the magnitude \
	and phase responses. \
	DSPAnalyze[f, {n1,n2}] and DSPAnalyze[f, {n1,n2}, {s1,s2}, {e1,e2}] \
	treat f as a function of two-variables n1 and n2. \
	The domain of the time-domain graph is n1 from s1 to e1 and \
	n2 from s2 to e2. \
	All non-variable symbols will be assigned a value of 1 when the \
	analyzer generates graphics (override this with options like a -> 2)."

(*  E N D     U S A G E     I N F O R M A T I O N  *)


Begin["`Private`"]


(*  M E S S A G E S  *)

ZTransform::notvalid = "The forward z-transform could not be found."
DSPAnalyze::hasval =
	"The variable '' has value so it could not be used as a \
	transform variable; '' was used instead."
DTFTransform::notvalid = "The forward DTFT could not be found."


(*  S U P P O R T I N G     F U N C T I O N S  *)

(*  dspAnalyzeQ  *)
dspAnalyzeQ[start_, end_] :=
	NumberQ[N[start]] && NumberQ[N[end]] && N[end >= start]

(*  checkvar  *)
SetAttributes[checkvar, {HoldFirst}]
checkvar[ xsym_, xval_, str_ ] := xsym /; SameQ[xsym, xval]
checkvar[ xsym_, xval_, str_ ] :=
	Block [ {sym},
		sym = Unique["z"];
		Message[ DSPAnalyze::hasval, str, sym ];
		sym ]

(*  formatting-- this works if and only if the variables do not appear  *)
(*      in a Block statement; i.e., they must be global to the package  *)
w/:  Format[ w ] := "w"
w1/: Format[ w1 ] := "w1"
w2/: Format[ w2 ] := "w2"

z/:  Format[ z ] := "z"
z1/: Format[ z1 ] := "z1"
z2/: Format[ z2 ] := "z2"

(*  printDefaults  *)
printDefaults[oplist_, varlist_, defaultvalues_] :=
	Block [	{},
		If [ EmptyQ[oplist],
		     Print[ "For plotting only, these symbols will" ];
		     Print[ "be assigned a value of 1:  ", varlist ],
		     Print[ "For plotting only, these symbols will" ];
		     Print[ "will be assigned default values:" ];
		     Print[ defaultvalues ] ] ]

(*  printStability  *)
printStability[ztrans_] :=
	Block [	{stable},
		stable = Stable[ztrans];
		Which [ SameQ[stable, True],
			  Print[ "The system is stable." ],
			SameQ[stable, False],
			  Print[ "The system is not stable." ],
			True,
			   Print[ "The system is stable if ", stable ] ];
		stable ]

(*  zToFreqResp  *)
zToFreqResp[ funct_, ztrans_, defaultvalues_, onedflag_, zrules_ ] :=
	Block [ {mag, plotrange, rm, rp, stable, zfreqresp},

		(*  Print the sequence and its z-transform  *)
		Print[ funct ];
		Print[ "has the following z-transform:" ];
		Print[ TheFunction[ztrans] ];

		(*  Print the Region of Convergence  *)
		Print[ "The region of convergence is:" ];
		rm = GetRMinus[ztrans];
		rp = GetRPlus[ztrans];
		If [ onedflag,
		     Print[ rm, " < |z| < ", rp ],
		     Print[ rm[[1]], " < |z1| < ", rp[[1]] ];
		     Print[ rm[[2]], " < |z2| < ", rp[[2]] ] ];

		(*  Print stability information  *)
		stable = printStability[ztrans];

		(*  From now on, the default values are used  *)
		If [ ! EmptyQ[defaultvalues],
		     Print[ "The default values are now being considered." ] ];

		(*  Show the Pole-Zero Plot  *)
		PoleZeroPlot[ztrans /. defaultvalues];

		If [ stable /. defaultvalues,
		     Print[ "Since the sequence is stable, the" ];
		     Print[ "frequency response can be computed" ];
		     Print[ "directly from the z-transform:" ];
		     zfreqresp = TheFunction[ztrans] /. zrules;
		     Print[ zfreqresp ];
		     plotrange = All;
		     mag = Log,

		     Print[ "Since the sequence is not stable, the" ];
		     Print[ "frequency response has no meaning, but" ];
		     Print[ "it will be plotted anyway.  However, the DC" ];
		     Print[ "(zero frequency) term will be Infinity which" ];
		     Print[ "will generate a warning from Mathematica." ];
		     plotrange = Automatic;
		     mag = Linear,
			  
		     Print[ "Even though the stability of the sequence" ];
		     Print[ "is not known, the frequency response will" ];
		     Print[ "still be plotted." ];
		     plotrange = Automatic;
		     mag = Linear ];

		{ mag, plotrange, zfreqresp } ]


(*  D S P A n a l y z e  *)

DSPAnalyze[f_, n_Symbol] := DSPAnalyze[f, n, 0]
DSPAnalyze[f_, n_Symbol, start_] := DSPAnalyze[f, n, start, start + 40]

DSPAnalyze[f_, n_Symbol, start_, end_, op___] :=
	Block [	{bogus, defaultvalues = {}, dtft, freqresp, fnumeric,
		 ftemp, funct, mag, oplist, plotrange = All, varlist,
		 zfreqresp, ztrans},

		oplist = ToList[op];
		funct = ToDiscrete[f];

		(*  DISCRETE-TIME ANALYSIS *)

                (*  Convert expression to plottable/functional form    *)
		(*  and replace constants like Pi with numerical value *)

		fnumeric = N[ TheFunction[ funct ] ];

                (*  Find all valueless symbols besides the time variable *)
		(*  The first parameter in Summation is a local symbol   *)

		ftemp = fnumeric /.
			  ( Summation[m_, l_, u_, inc_][expr_] :>
			    bogus[l, u, inc, GetVariables[expr /. m -> l]] );
		varlist = Select[GetVariables[ftemp /. oplist],
				 Function[var, ! SameQ[var, n]]];
		If [ (! EmptyQ[varlist]) || (! EmptyQ[oplist]),
		     defaultvalues = oplist ~Join~ Map[(#1 -> 1)&, varlist];
		     printDefaults[oplist, varlist, defaultvalues];
		     fnumeric = fnumeric /. defaultvalues ];

		(*  Plot "time"-domain	*)

		SequencePlot[ fnumeric, {n, start, end},
			      PlotLabel -> "Discrete-Time Domain Analysis" ];

		(*  Z-DOMAIN ANALYSIS *)

		(*    Find F[z]; if it exists, print it out, give stability  *)
		(*  information, and display a pole-zero plot.		     *)

		ztrans = ZTransform[funct, n, z];
		If [ ! SameQ[Head[ztrans], ZTransData],
		     Message[ZTransform::notvalid];
		     mag = Linear,
		     { mag, plotrange, zfreqresp } =
		       zToFreqResp[ funct, ztrans, defaultvalues,
				    True, (z -> Exp[I w]) ] ];

		(*  FREQUENCY-DOMAIN ANALYSIS  *)

		(*  Determine the frequency response  *)

		dtft = DTFTransform[funct, n, w, Simplify -> True];
		If [ ! SameQ[Head[dtft], DTFTData],
		     Message[DTFTransform::notvalid]; Return[ztrans] ];
		freqresp = TheFunction[dtft];

		(*  Plot the interesting section of the freq. response  *)

		If [ ! SameQ[freqresp, zfreqresp],
		     Print[ funct ];
		     Print[ "has the following frequency response:"];
		     Print[ freqresp ];
		     Print[ "where the frequency response is periodic" ];
		     Print[ "usually with period 2 Pi" ] ];

		freqresp = ToContinuous[freqresp];
		MagPhasePlot[ freqresp /. defaultvalues,
			      {w, -2 Pi, 2 Pi},
			      Domain -> Continuous, DomainScale -> Linear,
			      MagRangeScale -> mag, PhaseRangeScale -> Degree,
			      PlotRange -> plotrange ];

		(*  Return the z-transform  *)

		ztrans /. ( z -> checkvar[Global`z, Global`z, "z"] ) ] /;
	N [ dspAnalyzeQ[start, end] ]


DSPAnalyze[f_, {n1_Symbol, n2_Symbol}] := DSPAnalyze[f, {n1, n2}, {0, 0}]
DSPAnalyze[f_, {n1_Symbol, n2_Symbol}, start_] :=
	DSPAnalyze[f, {n1, n2}, start, start + {9, 9}]

DSPAnalyze[f_, {n1_Symbol, n2_Symbol}, start_, end_, op___] :=
	Block [	{coordlist, defaultvalues = {}, dtft, freqresp, ftemp, funct,
		 mag, nlist, oplist, plotrange = All, stable, varlist,
		 wlist, zfreqresp, zlist, ztrans},

		oplist = ToList[op];

		nlist = { n1, n2 };
		wlist = { w1, w2 };
		zlist = { z1, z2 };
		funct = ToDiscrete[f];

		(*  DISCRETE-TIME ANALYSIS *)

                (*  Convert expression to plottable/functional form    *)
		(*  and replace constants like Pi with numerical value *)

		fnumeric = N[ TheFunction[ funct ] ];

                (*  Find all valueless symbols besides the time variable *)
		(*  The first parameter in Summation is a local symbol   *)

		ftemp = fnumeric /.
			  ( Summation[m_, l_, u_, inc_][expr_] :>
			    bogus[l, u, inc, GetVariables[expr /. m -> l]] );
		varlist = Select[GetVariables[ftemp /. oplist],
			 	 Function[var,
					  ! SameQ[var,n1] && ! SameQ[var,n2]]];
		If [ (! EmptyQ[varlist]) || (! EmptyQ[oplist]),
		     defaultvalues = oplist ~Join~ Map[(#1 -> 1)&, varlist];
		     printDefaults[oplist, varlist, defaultvalues];
		     fnumeric = fnumeric /. defaultvalues ];

		(*  Generate three-dimensional coordinates for plot  *)

		SequencePlot[ fnumeric,
			      { n1, start[[1]], end[[1]] },
			      { n2, start[[2]], end[[2]] },
			      PlotLabel -> "Discrete-Time Domain Analysis" ];

		(*  Z-DOMAIN ANALYSIS  *)

		(*  Find F[z] and print it out.  *)

		ztrans = ZTransform[funct, nlist, zlist];
		If [ ! SameQ[Head[ztrans], ZTransData],
		     Message[ZTransform::notvalid];
		     mag = Linear,

		     { mag, plotrange, zfreqresp } =
		       zToFreqResp[ funct, ztrans, defaultvalues, False,
				ReplaceWith[zlist, {Exp[I w1], Exp[I w2]}] ] ];
 
		(*  FREQUENCY-DOMAIN ANALYSIS  *)

		(*  Determine the frequency response  *)

		dtft = DTFTransform[funct, nlist, wlist];
		If [ ! SameQ[Head[dtft], DTFTData],
		     Message[DTFT::notvalid]; Return[ztrans] ];
		freqresp = TheFunction[dtft];

		(*  Plot the interesting section of the freq. response  *)

		If [ ! SameQ[freqresp, zfreqresp],
		     Print[ funct ];
		     Print[ "has the following frequency response:" ];
		     Print[ freqresp ] ];

		(*  Making the freqresp periodic takes too long to plot: *)
		(*    Periodic[{2 Pi, 2 Pi}, {w1, w2}][freqresp]	 *)

		MagPhasePlot[ freqresp /. defaultvalues,
			      {w1, 0, 2 Pi}, {w2, 0, 2 Pi},
			      Domain -> Continuous, DomainScale -> Linear,
			      MagRangeScale -> mag, PhaseRangeScale -> Degree,
			      PlotRange -> plotrange ];

		(*  Return the z-transform  *)

		ztrans /. { z1 -> checkvar[Global`z1, Global`z1, "z1"],
			    z2 -> checkvar[Global`z2, Global`z2, "z2"] } ] /;

	N [ dspAnalyzeQ[ start[[1]], end[[1]] ] &&
	    dspAnalyzeQ[ start[[2]], end[[2]] ] ]


(*  E N D     P A C K A G E  *)

End[]
EndPackage[]

If [ TrueQ[ $VersionNumber >= 2.0 ],
     On[ General::spell ];
     On[ General::spell1 ] ];


(*  H E L P     I N F O R M A T I O N  *)

Combine[ SPfunctions, { DSPAnalyze } ]
Protect[ DSPAnalyze ]


(*  E N D I N G     M E S S A G E  *)

Print["The digital signal analyzer is now loaded."]
Null
